/*
	jGrouse framework
	Copyright 2007 by Denis Riabtchik 
	All rights reserved.
	
	Licensed under modified BSD license. See license.txt for details
*/

/**
	Module with code related to PropertyChangeSupport 
	@module jgrouse.util.PropertyChangeSupport
	@requires module-jgrouse.util.Sets
	@requires module-jgrouse.util.Catalog
	@requires module-jgrouse.string
*/

jgrouse.module({name :'jgrouse.util.PropertyChangeSupport', 
	requires : ['jgrouse.util.Sets', 'jgrouse.util.Catalog', 'jgrouse.string'],
	imports : ['jgrouse.util.Catalog', 'jgrouse.util.ObjectSet'],
	body: function(embed)
{
	eval(embed);
	
	/**
		Class describing events fired whenever properties are changed
		@class jgrouse.util.PropertyChangeEvent
	*/
	jgrouse.define('jgrouse.util.PropertyChangeEvent', null, function(salt)
	{
		eval(salt);
		
		return {
			/**
				Initialize event
				@constructor initialize
				@param {Object} sender - object that triggers property change event
				@param {String} propertyName - name of property being changed
				@param {Object} newValue - new value of property
				@param {Object} oldValue - old value of property, optional
			*/
			initialize : function(sender, propertyName, newValue, oldValue)
			{
				this._sender = sender;
				this._propertyName = propertyName;
				this._newValue = newValue;
				this._oldValue = oldValue;
				this._cancelled = false;
			},
			
			/**
				Retrieve sender of event
				@function {Object} getSender
			*/
			getSender : function()
			{
				return this._sender;
			},
			
			/**
				Retrieve property name of event
				@function {String} ?
			*/
			getPropertyName : function()
			{
				return this._propertyName;
			},
			
			/**
				Retrieve new value of event
				@function {Object} getNewValue
			*/
			getNewValue : function()
			{
				return this._newValue;
			},
			
			
			/**
				Retrieve old value of event
				@function {Object} getOldValue
			*/
			getOldValue : function()
			{
				return this._oldValue;
			},
			
			/**
				Mark event as cancelled. Framework would suppress further propagation of cancelled event
				@function cancel
			*/
			cancel : function()
			{
				this._cancelled = true;
			},
			
			/**
				Check if event was cancelled
				@function {boolean} isCancelled
			*/
			isCancelled : function()
			{
				return this._cancelled;
			}
			
			
		}
	});
	
	/**
		Listeners for property changes
		@class jgrouse.util.PropertyChangeListener
	*/
	jgrouse.define('jgrouse.util.PropertyChangeListener', null, function(salt)
	{
		eval(salt);
		
		return {
			
			/**
				Handler for changes of one property. 
				For example, if property name is <code><b>value</b></code> then the handler method would 
				be called <code><b>onValueChanged</b></code>
				@function on_PropertyName_Changed
				@param {Object} newValue - new value of property
				@param {Object} oldValue - old value of property, optional
			*/
			
			/**
				Handler for "trigger" events, i.e. when property value is being changed and then reset back.
				For example, if property name is <code><b>triggerProperty</b></code> then the handler method
				would be called <code><b>onTriggerProperty</b></code>
				@ifunction on_triggerPropertyName_
				@param {Object} newValue - new value of property
				@param {Object} oldValue - old value of property, optional
			*/

			/**
				General method to handle propety change events
				@function onPropertyChanged
				@param {jgrouse.util.PropertyChangeEvent} event - event that was triggered
			*/		
			onPropertyChanged : function(event)
			{
				jgrouse.NotImplemented(RTTI + '.onPropertyChanged');
			},
			
			/**
				Function that allows override of the handler method name for given property name.
				For example, if your app has a convention for handlers to be called when&lt;PropertyName&gt;
				then you can implement the following method for mapping:
				<pre><code>
	getHandlerName : function(propName)
	{
		return "when" + jgrouse.string.upperFirst(propName);
	}
				</code></pre>
				@function {String} getHandlerName
				@param {String} propertyName - name of property for which it is necessary to find handler
			*/
			getHandlerName : null
						
		}
	});
	
	
	/**
		Class implementing an "event hub" that notifies subscribers whenever certain property is changed
		@class jgrouse.util.PropertyChangeSupport
		@extends jgrouse.BaseObject
	*/
	jgrouse.define('jgrouse.util.PropertyChangeSupport', 'jgrouse.BaseObject', function(salt)
	{
		eval(salt);
		
		return {
			/**
				@constructor initialize
			*/
			initialize : function(owner)
			{
				_super(this, 'initialize');
				this._listeners = new ObjectSet(jgrouse.objectComparator);
				this._namedListeners = new Catalog();
				this._owner = owner;
			},
			
			/**
				Adds listener that would be triggered whenever certain property is changed
				@function addListener
				@paramset When registering a listener for specific property
				@param {String} propertyName - name of property to observe
				@param {jgrouse.util.PropertyChangeListener} listener - listener to trigger whenever given property is changed
				@paramset When registering a listener for changes of any property
				@param {jgrouse.util.PropertyChangeListener} listener - listener to trigger whenever any property is changed
			*/
			addListener : function(propertyNameOrListener, listener)
			{
				if (arguments.length === 2)
				{
					this._namedListeners.add(propertyNameOrListener, listener);
				}
				else
				{
					this._listeners.put(propertyNameOrListener);
				}				
			},
			
			/**
				Removes listener for property. Passed parameters must be <b>exactly</b> the same as 
				used to subscribe using {@link addListener}
				@function removeListener
				@paramset When unsubscribing from one property
				@param {String} propertyName - name of property to stop observing
				@param {jgrouse.util.PropertyChangeListener} listener - listener to be removed
				@paramset When unsubscribing a listener for changes of any property
				@param {jgrouse.util.PropertyChangeListener} listener - listener to be removed
			*/
			removeListener : function(propertyNameOrListener, listener)
			{
				if (arguments.length === 2)
				{
					this._namedListeners.remove(propertyNameOrListener, listener);
				}
				else
				{
					this._listeners.remove(propertyNameOrListener);
				}
			},
			
			/**
				Remove listener from all properties, regardless how it was registered - either for specific 
				property or for all properties
				@function removeListenerFull
				@param {jgrouse.util.PropertyChangeListener} listener - listener to be removed
			*/
			removeListenerFull : function(listener)
			{
				this._listeners.remove(listener);
				this._namedListeners.removeAll(listener);
			},
			
			/**
				Publish notification that a property had been changed
				@function firePropertyChange
				@param {String} propertyName - name of property that changes the value
				@param {Object} newValue - new value of property
				@param {Object} oldValue - old value of property
				@param {Object} sender - sender of the message, optional
			*/
			firePropertyChange : function(propertyName, newValue, oldValue, sender)
			{
				var event = new PropertyChangeEvent(sender? sender : this._owner, propertyName, newValue, oldValue);
				this.handlePropertyChangeEvent(event);
			},
			
			/**
			 * Publish event to listeners
			 * @function handlePropertyChangeEvent
			 * @param {jgrouse.util.PropertyChangeEvent} e - event to be passed to listeners 
			 */
			handlePropertyChangeEvent : function(event)
			{
				var athis = this;
				var propertyName = event.getPropertyName();
				this._namedListeners.forEachInBox(propertyName,
					function(listener)
					{
						athis.triggerListener(listener, event);
					}
				);
				if (!event.isCancelled())
				{	
					this._listeners.forEach(function(listener)
					{
						athis.triggerListener(listener, event);
					});
				}				
			},
			
			/**
				Finds handler method in given listener for given property. If listener has method 
				<code>getHandlerName</code> then that method would be used to get handler name.
				If property name  starts with <code><b>trigger</b></code> then the handler name 
				would be on&lt;PropertyName&gt;. Otherwise the property name would be 
				on&gt;PropertyName&gt;Changed 
				@function {String} getListenerMethod
				@param {jgrouse.util.PropertyChangeListener} listener - object containing methods to be triggered
				@param {String} propName - name of property
			*/
			getListenerMethod : function(listener, propName)
			{
				if (listener.getHandlerName)
				{
					var v = listener.getHandlerName(propName);
					if (v)
					{
						return v;
					}
				}
				var v = jgrouse.string.upperFirst(propName);
				var m = null;
				if (propName.indexOf('trigger') === 0)
				{
					m = 'on' + v;
					if (listener[m])
					{
						return m;
					}
				}
				m = 'on' + v + 'Changed';
				if (listener[m])
				{
					return m;
				}
				return 'onPropertyChanged';
			},
			
			/**
				Invokes listener for given event
				@function triggerListener
				@param {jgrouse.util.PropertyChangeListener} listener - listener object
				@param {jgrouse.util.PropertyChangeEvent} event - event that has to be processed
			*/
			triggerListener : function(listener, event)
			{
				if (listener != event.getSender())
				{
					var m = this.getListenerMethod(listener, event.getPropertyName());
					if (m != 'onPropertyChanged')
					{
						listener[m](event.getNewValue(), event.getOldValue());
					}
					else
					{
						if (listener[m])
						{
							listener[m](event);
						}
						else
						{
							if (event.getPropertyName().indexOf('trigger') != 0)
							{
								throw 'Cannot find appropriate handler for property ' + event.getPropertyName() 
									+ ' class=' + listener.RTTI;
							}
						}
					}					
				}
			},
			
			/**
				Call certain function for each listener
				@function forEach
				@param {function(listener, property)} iter - function to be called for each listener
			*/
			forEach : function(iter)
			{
				this._namedListeners.forEach(iter);
				this._listeners.forEach(function(item)
				{
					iter(item, null);
				});
			}
		}
	});
}});